package org.hawk.gwt.ppc.parser.impl;

import java.util.ArrayList;
import java.util.List;

import org.hawk.gwt.ppc.parser.BlockDescriptor;
import org.hawk.gwt.ppc.parser.ClassDescriptor;
import org.hawk.gwt.ppc.parser.ClassDescriptor.ClassDescriptorType;
import org.hawk.gwt.ppc.parser.AnnotationDescriptor;
import org.hawk.gwt.ppc.parser.ConstructorDescriptor;
import org.hawk.gwt.ppc.parser.Descriptor;
import org.hawk.gwt.ppc.parser.FieldDescriptor;
import org.hawk.gwt.ppc.parser.JavaKeywords;
import org.hawk.gwt.ppc.parser.MethodDescriptor;
import org.hawk.gwt.ppc.parser.PreprocessorParseException;
import org.hawk.gwt.ppc.parser.Token;
import org.hawk.gwt.ppc.utils.PreprocessorException;
import static org.hawk.gwt.ppc.parser.impl.DescriptorParserUtils.*;

final class ClassDescriptorParser implements DescriptorParser {

	/**
	 * Accepts only top-level classes.
	 * As for member classes, local classes and anonymous ones
	 * it should be directly invoked by corresponding instructions.
	 */
	public boolean accepts(Token token) {
		if (token.isWord()) {
			return token.equalsTo(JavaKeywords.PUBLIC) ||
					token.equalsTo(JavaKeywords.CLASS) ||
					token.equalsTo(JavaKeywords.INTERFACE) ||
					token.equalsTo(JavaKeywords.ENUM) ||
					token.equalsTo(JavaKeywords.FINAL) ||
					token.equalsTo(JavaKeywords.ABSTRACT);
		}
		return token.equalsTo('@');
	}

	/*
	 * (non-Javadoc)
	 * @see org.hawk.gwt.ppc.parser.impl.DescriptorParser#parse(org.hawk.gwt.ppc.parser.impl.TokenBasedJavaParser, org.hawk.gwt.ppc.parser.impl.Tokenizer)
	 */
	public Descriptor parse(TokenBasedJavaParser parser, TokenSource tokenizer, Object...params)
			throws PreprocessorException {
		Token first = tokenizer.current();
		List<String> extendsList = new ArrayList<String>(1);
		List<String> implementsList = new ArrayList<String>(2);
		@SuppressWarnings("unchecked")
		List<String> modifiersList = params.length > 0 ? (List<String>) params[0] : new ArrayList<String>(1);
		
		List<MethodDescriptor> methods = new ArrayList<MethodDescriptor>();
		List<BlockDescriptor> initilizers = new ArrayList<BlockDescriptor>(1);
		List<ConstructorDescriptor> constructors = new ArrayList<ConstructorDescriptor>(2);
		List<ClassDescriptor> declaredClasses = new ArrayList<ClassDescriptor>(1);
		List<FieldDescriptor> fields = new ArrayList<FieldDescriptor>();
		List<AnnotationDescriptor> annotations = new ArrayList<AnnotationDescriptor>(1);
		List<Descriptor> children = new ArrayList<Descriptor>();
		
		String type;
		if (params.length > 1) {
			type = (String) params[1];
		} else if (params.length == 1) {
			type = parseType(tokenizer);
		} else {
			type = parseModifiersAndType(parser, tokenizer, 
				modifiersList, annotations);
		}
		
		String name;
		if (params.length > 2) {
			name = "";// anonymous
			extendsList.add((String)params[2]);
		} else {
			name = parseNameAndInheritance(tokenizer, extendsList, implementsList);
		} 
		
		if (!tokenizer.current().equalsTo('{')) {
			throw new PreprocessorParseException(tokenizer.current());
		}
		lookupNext(tokenizer, false);
		
		ClassDescriptorType typeEnum = ClassDescriptorType.valueOf(type.toUpperCase());
		
		if (typeEnum.equals(ClassDescriptorType.ENUM)) {
			while (!tokenizer.current().equalsTo(';') && 
					!tokenizer.current().equalsTo('}')) {
				MemberUtils.parseEnumField(parser, tokenizer, fields, name);
			}
			if (tokenizer.current().equalsTo(';')) {
				lookupNext(tokenizer, false);
			}
		}
		
		while (!tokenizer.current().equalsTo('}')) {
			if(tokenizer.current().equalsTo(';')) {
				lookupNext(tokenizer, false);
			} else {
				MemberUtils.parseClassMember(tokenizer, parser, methods, initilizers, 
					constructors, declaredClasses, fields, children);
			}
		}
		
		Token lastToken = tokenizer.current();
		
		lookupNext(tokenizer, true);
		
		return new ClassDescriptorImpl(typeEnum, 
				name, first, lastToken, children, modifiersList, extendsList, implementsList,
				methods, initilizers, constructors, declaredClasses, fields, annotations);
	}

	private String parseModifiersAndType(TokenBasedJavaParser parser,
			TokenSource tokenizer, List<String> modifiersList, 
			List<AnnotationDescriptor> annotations) 
			throws PreprocessorException {
		
		Token token = tokenizer.current();

		if (!tokenizer.hasNext()) {
			throw new PreprocessorParseException("Unexpected end of file", token);
		}
		
		if (AnnotationDescriptorParser.parseAnnotations(parser, tokenizer, annotations)) {
			return JavaKeywords.ANNOTATION;
		}
		
		readModifiers(modifiersList, tokenizer);
		
		return parseType(tokenizer);
	}

	private String parseType(TokenSource tokenizer) throws PreprocessorException {
		Token token = tokenizer.current();
		
		if (token.equalsTo('@')) {
			lookupNext(tokenizer, false);
			lookupNext(tokenizer, false);
			return JavaKeywords.ANNOTATION;
		}
		lookupNext(tokenizer, false);
		return token.toString();
	}

	private String parseNameAndInheritance(
			TokenSource tokenizer, List<String> extendsList, 
			List<String> implementsList) 
			throws PreprocessorException {
		StringBuilder name = new StringBuilder();
		
		readTypeName(name, tokenizer, false);
		
		if (tokenizer.current().equalsTo(JavaKeywords.EXTENDS)) {
			parseTypesList(tokenizer, extendsList);
		}
		
		if (tokenizer.current().equalsTo(JavaKeywords.IMPLEMENTS)) {
			parseTypesList(tokenizer, implementsList);
		}
		
		return name.toString();
	}

	private void parseTypesList(TokenSource tokenizer,
			List<String> typesList) 
			throws PreprocessorException {
		do {
			lookupNext(tokenizer, false);
			if (tokenizer.current().equalsTo('{')) {
				throw new PreprocessorParseException("Unexpected token", tokenizer.current());
			}
			StringBuilder type = new StringBuilder();
			readTypeName(type, tokenizer, false);
			typesList.add(type.toString());
			
		} while (tokenizer.current().equalsTo(','));
	}
}
